<template>
  <div class="my-scene-box" ref="mySceneBox"></div>
</template>

<script>
// 导入整个 three.js核心库
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import WebGL from "@/utils/WebGL.js";

export default {
  name: "demoOne",
  data() {
    return {
      scene: null, // 场景
      renderer: null, // 渲染器
      controls: null, // 轨道控制器
      directionalLight: null, // 平行光
      mesh: null,
    };
  },
  mounted() {
    this.initThree();
  },
  methods: {
    initThree() {
      // 浏览器WebGL兼容性判断
      if (!WebGL.isWebGLAvailable()) {
        const warning = WebGL.getWebGLErrorMessage();
        this.$refs.mySceneBox.appendChild(warning);
        return;
      }
      // 1.创建场景对象Scene
      this.scene = new THREE.Scene();
      // 2.创建多个几何模型 并生产 mesh(网格模型) 数组
      const meshList = this.initGeometry();

      // 场景对象scene的方法.add()把网格模型mesh加入场景中
      meshList.forEach((mesh) => {
        this.scene.add(mesh); //网格模型添加到场景中  默认是在 （0，0，0）坐标
      });

      // 辅助坐标系  参数250表示坐标系大小，可以根据场景大小去设置
      // var axisHelper = new THREE.AxisHelper(250); // 构造函数改名了
      const axisHelper = new THREE.AxesHelper(250);
      this.scene.add(axisHelper);

      // 4.光源设置
      const lightList = this.initLight();
      lightList.forEach((light) => {
        this.scene.add(light);
      });

      // 5.相机设置
      var width = this.$refs.mySceneBox.clientWidth; //窗口宽度
      var height = this.$refs.mySceneBox.clientHeight; //窗口高度
      var k = width / height; //窗口宽高比
      var s = 300; //三维场景显示范围控制系数，系数越大，显示的范围越大
      //创建相机对象
      this.camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000);
      this.camera.position.set(200, 500, 500); //设置相机位置
      this.camera.lookAt(this.scene.position); //设置相机方向(指向的场景对象)
      // 6.创建渲染器对象
      this.renderer = new THREE.WebGLRenderer();
      this.renderer.setSize(width, height); //设置渲染区域尺寸
      this.renderer.setClearColor(0xb9d3ff, 1); //设置背景颜色

      this.renderer.shadowMap.enabled = true;
      this.renderer.shadowMap.type = THREE.PCFSoftShadowMap; // default

      this.$refs.mySceneBox.appendChild(this.renderer.domElement); //body元素中插入canvas对象

      //执行渲染操作   指定场景、相机作为参数
      this.loopRender();

      // 新增轨道控制器
      this.controls = new OrbitControls(this.camera, this.renderer.domElement); //创建控件对象
    },

    // 生成光源
    initLight() {
      /** 光源分类      Threejs提供了一些光源辅助对象，就像AxesHelper可视化显示三维坐标轴一样显示光源对象,通过这些辅助对象可以方便调试代码，查看位置、方向。
       * 1.AmbientLight      环境光
       * 2.PointLight        点光源                  PointLightHelper        点光源辅助对象
       * 3.DirectionalLight  平行光（比如太阳光）      DirectionalLightHelper  平行光辅助对象
       * 4.SpotLight         聚光源                  SpotLightHelper         聚光源辅助对象
       *
       * 基类Light属性:  color(颜色)、intensity（强度）
       *
       * 光照计算算法
       *    Three.js渲染的时候光照计算还是比较复杂的，这里不进行深入介绍，只给大家说下光源颜色和网格模型Mesh颜色相乘的知识，如果你有兴趣可以学习计算机图形学或者WebGL教程。
       *    Threejs在渲染的时候网格模型材质的颜色值mesh.material.color和光源的颜色值light.color会进行相乘，简单说就是RGB三个分量分别相乘。
       *    平行光漫反射简单数学模型：漫反射光的颜色 = 网格模型材质颜色值 x 光线颜色 x 光线入射角余弦值
       *
       *    漫反射数学模型RGB分量表示：(R2,G2,B2) = (R1,G1,B1) x (R0,G0,B0) x cosθ
       *          R2 = R1 * R0 * cosθ
                  G2 = G1 * G0 * cosθ
                  B2 = B1 * B0 * cosθ
       *
       * 立体效果:仅仅使用环境光的情况下，你会发现整个立方体没有任何棱角感，这是因为环境光只是设置整个空间的明暗效果。如果需要立方体渲染要想有立体效果，需要使用具有方向性的点光源、平行光源等。
       */

      /**
       *  在具有方向光源的作用下，物体会形成阴影投影效果。
       */

      // **********************     参考地址：https://blog.csdn.net/weixin_40583266/article/details/112768512
      const lightList = [];
      // // 1.环境光    环境光颜色与网格模型的颜色进行RGB进行乘法运算
      // const ambient = new THREE.AmbientLight(0x444444); // 0xffffff 更亮
      // lightList.push(ambient);

      // // 2.点光源  可以设置多个点光源
      // const point = new THREE.PointLight(0xffffff);
      // point.position.set(400, 400, 400); //点光源位置
      // lightList.push(point);

      // // // 2.1 点光源辅助对象
      // // PointLightHelper( light：点光, sphereSize:点光对象的大小, color：颜色 )
      // var sphereSize = 10;
      // var pointLightHelper = new THREE.PointLightHelper(point, sphereSize);
      // lightList.push(pointLightHelper);

      // // 3. 平行光
      // const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
      // // // 设置光源的方向：通过光源position属性和目标指向对象的position属性计算
      // directionalLight.position.set(60, 100, 40);
      // // 设置用于计算阴影的光源对象
      // directionalLight.castShadow = true;
      // // 设置计算阴影的区域，最好刚好紧密包围在对象周围
      // // 计算阴影的区域过大：模糊  过小：看不到或显示不完整
      // directionalLight.shadow.camera.near = 0.5;
      // directionalLight.shadow.camera.far = 300;
      // directionalLight.shadow.camera.left = -50;
      // directionalLight.shadow.camera.right = 50;
      // directionalLight.shadow.camera.top = 200;
      // directionalLight.shadow.camera.bottom = -100;
      // // 设置mapSize属性可以使阴影更清晰，不那么模糊
      // directionalLight.shadow.mapSize.set(1024, 1024);
      // // console.log(directionalLight.shadow.camera);
      // // 方向光指向对象网格模型mesh2，可以不设置，默认的位置是0,0,0
      // // directionalLight.target = mesh2;
      // lightList.push(directionalLight);
      // this.directionalLight = directionalLight;

      // // 3.1 平行光辅助对象   DirectionalLightHelper( light, size, color )
      // const directionalLighthelper = new THREE.DirectionalLightHelper(
      //   directionalLight,
      //   50,
      //   0xffffff
      // );
      // lightList.push(directionalLighthelper);

      // 4.聚光光源
      // const spotLight = new THREE.SpotLight(0xffffff);
      // // 设置聚光光源位置
      // spotLight.position.set(200, 200, 200);
      // // 聚光灯光源指向网格模型mesh2
      // // spotLight.target = mesh2;
      // // 设置聚光光源发散角度
      // spotLight.angle = Math.PI / 6;
      // lightList.push(spotLight);

      // 聚光光源
      var spotLight = new THREE.SpotLight(0xffffff);
      // // 设置聚光光源位置
      spotLight.position.set(200, 200, 200);
      // // 设置聚光光源发散角度
      spotLight.angle = Math.PI / 6;
      // // 设置用于计算阴影的光源对象
      spotLight.castShadow = true;
      // // 设置计算阴影的区域，注意包裹对象的周围   ****！！！！ 这东西有毒
      // spotLight.shadow.camera.near = 1;
      // spotLight.shadow.camera.far = 200;
      // spotLight.shadow.camera.fov = 20;
      // // spotLight.target = this.mesh;

      lightList.push(spotLight);

      //------------ 下面代码也可以实现
      // const spotLight = new THREE.SpotLight(0xffffff, 1);
      // // 设置聚光灯的位置
      // spotLight.position.set(150, 40, 35);
      // // 光线散射角度, 最大为Math.PI/2
      // spotLight.angle = Math.PI / 6;
      // // 聚光锥的半影衰减百分比。在0和1之间的值。 默认值 — 0.0。
      // // spotLight.penumbra = 0.05; //半影
      // // 沿着光照距离的衰减量 设置为等于2将实现现实世界的光衰减 默认1
      // // spotLight.decay = 2; //衰落
      // // 如果非零，那么光强度将会从最大值当前灯光位置处按照距离线性衰减到0. 默认为0.0
      // // spotLight.distance = 200;
      // // 此属性设置为 true 聚光灯将投射阴影。
      // // 警告: 这样做的代价比较高而且需要一直调整到阴影看起来正确。
      // // 不是所有的光源都可以投射阴影,这里使用聚点光源可以产生阴影
      // spotLight.castShadow = true;
      // lightList.push(spotLight);

      // 聚光灯显示助手SpotLightHelper( light:灯光, color：颜色 )
      const spotLightHelper = new THREE.SpotLightHelper(spotLight, 0xdfdfdf);
      lightList.push(spotLightHelper);

      return lightList;
    },
    // 创建多个几何模型  返回 mesh 对象数组
    initGeometry() {
      const returnList = [];

      // 生成立方体网格模型
      var geometry = new THREE.BoxGeometry(40, 100, 40);
      var material = new THREE.MeshLambertMaterial({ color: 0x0000ff }); //材质对象
      var mesh = new THREE.Mesh(geometry, material); //网格模型对象
      mesh.castShadow = true; // 设置产生投影的网格模型
      returnList.push(mesh);
      this.mesh = mesh;

      const geometry1 = new THREE.SphereGeometry(20, 25, 25);
      const sphere = new THREE.Mesh(geometry1, material);
      sphere.castShadow = true;
      sphere.position.set(70, 20, 0);
      returnList.push(sphere);

      //创建一个平面几何体作为投影面
      var planeGeometry = new THREE.PlaneGeometry(500, 500);
      var planeMaterial = new THREE.MeshLambertMaterial({
        color: 0xffffff,
        // color: 0x999999,
      });
      // 平面网格模型作为投影面
      var planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
      planeMesh.rotateX(-Math.PI / 2); //旋转网格模型
      planeMesh.position.y = -50; //设置网格模型y坐标
      // 设置接收阴影的投影面
      planeMesh.receiveShadow = true;
      returnList.push(planeMesh);

      return returnList;
      /**   总结:  材质和模型对象对应关系
       *    使用对应关系
       *    点模型Points、线模型Line、网格网格模型Mesh都是由几何体Geometry和材质Material构成，这三种模型的区别在于对几何体顶点数据的渲染方式不同，
       *
       *  点模型：Points
       *  渲染模式：
       *        点渲染模式：点模型Points就是几何体的每一个顶点数据渲染为一个方形区域，方形区域的大小可以设置。
       *  点材质：PointsMaterial       点材质对象
       *
       *  线模型：Line、LineLoop、LineSegments
       *  渲染模式：线渲染模式
       *  线材质:
       *          LineBasicMaterial    直线基础材质对象
       *          LineDashedMaterial   虚线材质对象
       *
       *  网格模型：Mesh、SkinnedMesh（骨骼网格模型）
       *  渲染模式：
       *         线渲染模式： 线模型除了Line还有LineLoop和LineSegments,LineLoop和Line区别是连线的时候会闭合把第一个顶点和最后一个顶点连接起来，
       *                    LineSegments则是顶点不共享，第1、2点确定一条线，第3、4顶点确定一条直线，第2和3点之间不连接
       *  网格材质：
       *          MeshBasicMaterial    基础网格材质对象
       *          MeshLambertMaterial  慢反射材质
       *          MeshPhongMaterial    高光反射材质
       *          PBR材质（比MeshPhongMaterial效果更好）：MeshStandardMaterial、MeshPhysicalMaterial
       *          MeshDepthMaterial    网格深度材质
       *          MeshNormalMaterial   网格法向量材质
       *
       *  精灵模型：Sprite
       *  精灵材质：SpriteMaterial       精灵Sprite材质
       *
       * 自定义着色器材质:
       *          RawShaderMaterial
       *          ShaderMaterial
       * */
    },

    // 渲染函数
    loopRender() {
      // 平行光没办法做  动态投影  因为 平行光存在指向问题
      // this.directionalLight.rotateY(0.01); //旋转角速度0.001弧度每毫秒
      requestAnimationFrame(this.loopRender);
      this.renderer.render(this.scene, this.camera); //执行渲染操作
    },
  },
};
</script>
<style>
.my-scene-box {
  width: 90%;
  height: 500px;
  margin: 0 auto;
  border: 1px solid #000;
}
</style>
